/*
Kakine
ActionScript Framework
Copyright © 2011, PUNY, LLC.

This file is part of KAKINE.

KAKINE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

KAKINE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with KAKINE.  If not, see <http://www.gnu.org/licenses/>.
*/ 
package com.puny.ball.utils
{
	import flash.geom.Point;
	
	/**
	 * Collision between Circle and Arc 
	 * @author toritori
	 * 
	 */
	public class CollisionPA extends Object implements ICollision
	{
		private const RADIAN:Number = Math.PI / 180;
		private const TWO_PI:Number = 2 * Math.PI;
		private const XA:Vector2D = new Vector2D(1,0);
		private var _c:Vector2D = new Vector2D();
		private var _t:Number;
		private var _t2:Number;
		private const _p0:Vector2D = new Vector2D();
		private const _p:Vector2D = new Vector2D();
		private const _pt:Vector2D = new Vector2D();
		private const _p_t:Vector2D = new Vector2D();
		private const _pt0:Vector2D = new Vector2D();
		private const _n:Vector2D = new Vector2D();
		private const _reflex:Vector2D = new Vector2D();
//		private const _result:Vector2D = new Vector2D();
		private const _mirror:Vector2D = new Vector2D();
		private const _anglePt:Vector2D = new Vector2D();
		private var _nl2:Number;
		private var _r:Number;
		private var _min:Number;
		private var _rotation:Number = 0;
		private var _rotatedMin:Number;
		private var _rotatedMax:Number;
		private var _max:Number;
		private var _sign:Number;
		private var _angle:Number;
		/**
		 * 
		 * @param c a center of arc
		 * @param r a radius
		 * @param r1 an angle for arc.
		 * @param r2 an angle for arc.
		 * 
		 */
		public function CollisionPA(c:Vector2D,r:Number,r1:Number=0,r2:Number=0)
		{
			_c.x = c.x;
			_c.y = c.y;
			_r = r;
			_min = r1 > r2 ? r2 : r1;
			_max = r1 > r2 ? r1 : r2;
			_angle = _max - _min;
			calcRotated();
			super();
		}
		
		public function get center():Vector2D {
			return _c;
		}
		
		public function set radius(value:Number):void {
			_r = value;
		}
		
		/**
		 * Check a collision between this arc and a given point.
		 * @param p0 an initial position for the point.
		 * @param p an expected final relative position form <code>p0</code> for the point.
		 * @return <code>true</code> if a collision happens before reaching at the final position.
		 * 
		 */
		public function setParticle(p0:Vector2D,p:Vector2D):Boolean {
//			trace("p0=",p0,"p=",p);
//			trace("r=",_r, "center=", _c);
//			trace("rotation=", _rotation);
			_p.x = p.x;
			_p.y = p.y;
			_p0.x = p0.x;
			_p0.y = p0.y;
			
			var a:Number = p.x * p.x + p.y * p.y;
			var b:Number = 2 * (p0.x * p.x - p.x * _c.x + p0.y * p.y - p.y * _c.y);
			var c:Number = p0.x * p0.x + p0.y * p0.y + _c.x * _c.x  + _c.y * _c.y - 2 * (p0.x * _c.x + p0.y * _c.y) - _r*_r;
			var d:Number = b * b - 4 * a * c;
			var sd:Number;
			var t:Number;
			var s:Number;
			_t2 = NaN;
			if (a != 0) {
				if (d < 0) {
					return false;
				}
				sd = Math.sqrt(d);
				t = (-b+sd)/2/a;
				s = (-b-sd)/2/a;
				if (1>=t && t>0 && 1>=s && s>0) {
//					_t = t < s ? t : s;
//					_t2 = t < s ? s : t;
					// always t > s!!
					_t = s;
					_t2 = t;
					prepareVariables();
					return true;
				} else if (1>=t && t>0) {
					_t = t;
					prepareVariables();
					return true;
				} else if (1>=s && s>0) {
					_t = s;
					prepareVariables();
					return true;
				}
			}
			//In a case of b == 0, this means perticle is not moving. So _t should be Nan, too.
			_t = NaN;
			return false;
		}
		
		/**
		 * coefficient for Collison Point Calculation
		 * @return coefficient. It can be NaN if no collisions happen.
		 * <listing version="3.0">
		 * var p:Vector2D = new Vector2D(100,100);
		 * var r:Number = 30;
		 * var cpa:CollisionPA = new CollisionPA(p, r);
		 * 
		 * var p0:Vector2D = new Vector2D(200, 115);
		 * var dir:Vector2D = new Vector2D(-150, 0);
		 * if (pt.setParticle(p0,p)) {
		 * 	var t:Number = pt.collisionPoint();
		 *  trace("collides at (",p0.x + t * dir.x,",", p0.y + t * dir.y,")");
		 * }
		 * </listing> 
		 */
		public function collisionPoint():Number {
			if (checkAngle(_t)) {
				if (!isNaN(_t2) && !checkAngle(_t2)) {
					_t = _t2;
				} else {
					_t = NaN;
				}
			}
			return _t;
		}
		
		private function checkAngle(t:Number):Boolean {
//			var p:Vector2D = _p.multiply(t).add(_p0).subtract(_c) as Vector2D;
			_anglePt.x = (_p.x * t + _p0.x - _c.x)/_r;
			_anglePt.y = (_p.y * t + _p0.y - _c.y)/_r;
			
			var a:Number = Math.acos(XA.dot(_anglePt));
			if (_anglePt.y < 0) {
				a = TWO_PI - a;
			}
			if (a < _rotatedMin && _rotatedMax > TWO_PI) {
				a += TWO_PI;
			}
			return _min != _max && _rotatedMin < a && a < _rotatedMax;
		}
		
		private function prepareVariables():void {
			//			_pt = _p.multiply(_t);
			_pt.x = _p.x * _t;
			_pt.y = _p.y * _t;
			
			//			_pt0 = _p0.add(_pt);
			_pt0.x = _p0.x + _pt.x;
			_pt0.y = _p0.y + _pt.y;
			//			_n = _pt0.subtract(_c) as Vector2D;
			_n.x = _pt0.x - _c.x;
			_n.y = _pt0.y - _c.y;
			_nl2 = (_n.x * _n.x + _n.y * _n.y) / 2;
		}
		
		/**
		 * 
		 * @return a new position projected around a normal vector 
		 * 
		 */
		public function reflex():Vector2D {
			var t:Number = _t == 1 ? .1 : 1 - _t;
//			var p:Point = _p.multiply(t);
			_p_t.x = _p.x * t;
			_p_t.y = _p.y * t;
//			var result:Vector2D = p.subtract(_n.multiply(2 * _n.dot(p) / _nl2)).add(_pt0) as Vector2D;
			
			var n_p:Number = _n.dot(_p_t) / _nl2;
			_reflex.x = _p_t.x - _n.x * n_p + _pt0.x;
			_reflex.y = _p_t.y - _n.y * n_p + _pt0.y;
			
//			_result.x = _reflex.x - _c.x;
//			_result.y = _reflex.y - _c.y;
//			if (_result.length <= _r) {
//				_reflex.x *= 1.1;
//				_reflex.y *= 1.1;
//			}
			return _reflex;
		}
		
		/**
		 * 
		 * @return a projective position around the collided surface
		 * 
		 */
		public function mirror():Vector2D {
			var n_pt:Number = _n.dot(_pt) / _nl2;
			_mirror.x = _n.x * n_pt - _pt.x + _pt0.x;
			_mirror.y = _n.y * n_pt - _pt.y + _pt0.y;
			return _mirror;
//			return _n.multiply(2 * _n.dot(_pt) / _nl2).subtract(_pt).add(_pt0) as Vector2D;			
		}
		
		/**
		 * 
		 * @param value not radian.
		 * 
		 */
		public function set rotation(value:Number):void {
			_rotation = (value%360) * RADIAN;
			calcRotated();
		}
		
		private function calcRotated():void {
			_rotatedMin = _min + _rotation;
			_rotatedMax = _max + _rotation;
			if (_rotatedMin > TWO_PI) {
				_rotatedMin -= TWO_PI;
			} else if (_rotatedMin < 0) {
				_rotatedMin += TWO_PI;
			}
			if (_rotatedMax > TWO_PI + _angle) {
				_rotatedMax -= TWO_PI;
			} else if (_rotatedMax < _angle) {
				_rotatedMax += TWO_PI;
			}
		}
	}
}